跳到主要内容

Python 装饰器

双层装饰器

双层装饰器通常用于在不需要额外参数的情况下增强函数的行为。以下是一个简单的日志装饰器,用于在函数调用前后打印日志:

def simple_logger(func):
def wrapper(*args, **kwargs):
print(f"Before calling {func.__name__}")
result = func(*args, **kwargs)
print(f"After calling {func.__name__}")
return result
return wrapper

@simple_logger
def say_hello(name):
print(f"Hello, {name}!")

say_hello("Alice")

这个装饰器simple_logger捕捉到了say_hello函数的调用,并在调用前后打印了日志。

wraps 装饰器

wrapsfunctools 模块中的一个装饰器,用于在定义装饰器时保留原函数的元信息,如函数的名称、文档字符串(docstring)、注解和模块信息。当你创建自定义装饰器时,wraps 可以帮助确保装饰过的函数看起来仍然像未装饰前的原始函数。

使用装饰器通常会改变函数的 __name____doc__ 属性,因为装饰器通常会返回一个新的函数。如果不使用 wraps,那么装饰过的函数的这些属性会丢失,这可能会对调试、使用帮助文档等操作造成不便。wraps 通过从原始函数复制相关的元信息到装饰器函数,帮助保持这些属性不变。

下面是一个简单的例子来说明 wraps 的使用:

from functools import wraps

def my_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = f(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper

@my_decorator
def say_hello():
"""Greet the user"""
print("Hello!")

say_hello()
print(say_hello.__name__) # Without wraps, this would return 'wrapper' instead of 'say_hello'
print(say_hello.__doc__) # This prints the docstring of 'say_hello' function: "Greet the user"

在这个例子中,@wraps(f) 装饰器确保了 wrapper 函数保留了 say_hello 函数的名字和文档字符串。这样,当你查询 say_hello 函数的 __name____doc__ 属性时,你得到的是原始函数的信息,而不是 wrapper 函数的信息。

三层结构的装饰器

  • 最外层:接受装饰器的参数(例如,一个超时时间),并返回中间层的装饰器函数。
  • 中间层:接受被装饰的函数,并返回内层的包装函数。
  • 内层(包装函数):包含实际的逻辑,如在调用原函数前后执行某些操作,这里可以访问到最外层的参数和被装饰的函数。

和二层结构的装饰器的区别

  • 没有额外参数的装饰器(两层结构):直接装饰一个函数,不需要外部参数来调整装饰器的行为。
  • 有额外参数的装饰器(三层结构):需要外部参数来调整装饰器的行为,因此增加了一层来处理这些参数。
def decorator_with_args(arg1, arg2):  # 最外层,接收装饰器参数
def decorator(func): # 中间层,接收函数
def wrapper(*args, **kwargs): # 内层,实现具体逻辑
# 使用 arg1 和 arg2
return func(*args, **kwargs)
return wrapper
return decorator

示例

这个装饰器timeout_decorator接受一个timeout参数,然后返回一个装饰器decorator,该装饰器再返回实际的包装函数wrapper。包装函数中,我们检查了函数执行时间是否超过了设定的超时时间,并在超时时打印警告。这种方式允许装饰器根据传入的参数调整其行为,展示了三层装饰器结构的灵活性。

from functools import wraps
import time

def timeout_decorator(timeout):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
if end_time - start_time > timeout:
print(f"Warning: {func.__name__} execution time exceeded {timeout} seconds")
return result
return wrapper
return decorator

@timeout_decorator(timeout=2)
def slow_function(delay):
print(f"Running with delay {delay}")
time.sleep(delay)

slow_function(1) # Should not trigger the warning
slow_function(3) # Should trigger the warning